#include "WorkerThread.h"
#include "Config.h"
#include <QRandomGenerator>
#include <QDebug>
#include <QMatrix4x4>
#include <QVector2D>
#include <QVector4D>
#include <cmath>
#include <functional>
#include "Mesh.h"
#include "Texture.h"

WorkerThread::WorkerThread()
{
    pixelTotal = Config::width() * Config::height();
    screenBuffer = new uint8_t[pixelTotal * 3];
    zBuffer = new float[pixelTotal];
}

WorkerThread::~WorkerThread()
{
    delete screenBuffer;
    delete zBuffer;
}

void WorkerThread::Flush()
{
    emit Show((const uint8_t*)screenBuffer);
}

void WorkerThread::run()
{
    auto mesh = Mesh::CreateCubeTexture(2);
//    auto mesh = Mesh::CreateJioJioTexture(0.5);
//    auto mesh = Mesh::CreateSingleTriangleTexture(0.5);
//    auto mesh = Mesh::CreateSinglePlantTexture(0.5);
    auto texture = Texture(R"(./test.gif)");
    int counter = 0;
    while (!ShouldStop)
    {
        counter += 5;
        counter %= 360;
        ClearZBuffer();
        ClearScreenBuffer();

        QMatrix4x4 modelMatrix;
        modelMatrix.translate(0, 0, 0);
        modelMatrix.rotate((float)counter, 1, 1, 1);
        modelMatrix.scale(1, 1, 1);

        QMatrix4x4 viewMatrix;
        viewMatrix.translate(0, -10, 0);
        viewMatrix.rotate(-90, 1, 0, 0);
        viewMatrix = viewMatrix.inverted();

        QMatrix4x4 chiralMatrix;
        chiralMatrix.setRow(2, QVector4D(0, 0, -1, 0));

        // 该函数生成左手系的projMatrix
        QMatrix4x4 projMatrix;
        projMatrix.perspective(45, (float)Config::width() / (float)Config::height(), 1, 100);

        for (int i = 0; i < mesh.GetTriangleTotal(); ++i)
        {
            // 几何阶段
            QVector4D positions[3];
            QVector4D views[3];
            for (int k = 0; k < 3; ++k)
            {
                // 顶点坐标转换
                auto pp = mesh.GetTriangleVertex(i, k).GetPosition();
                positions[k] = QVector4D(pp[0], pp[1], pp[2], 1);
                views[k] = chiralMatrix * viewMatrix * modelMatrix * positions[k];
                positions[k] = projMatrix * views[k];
            }

            // 剪裁
            auto ifInCvv = std::function([](const QVector4D& pos)
            {
                auto x = (pos.x() < pos.w()) && (pos.x() > -pos.w());
                auto y = (pos.y() < pos.w()) && (pos.y() > -pos.w());
                auto z = (pos.z() < pos.w()) && (pos.z() > -pos.w());
                return x && y && z;
            });

            auto ifBackFace = std::function([](const QVector4D* poss)
            {
                auto v1 = poss[1].toVector3D() - poss[0].toVector3D();
                auto v2 = poss[2].toVector3D() - poss[1].toVector3D();
                auto normal = QVector3D::crossProduct(v1, v2);
                return QVector3D::dotProduct(normal, poss[0].toVector3D()) > 0;
            });

            if(!ifInCvv(positions[0]) && !ifInCvv(positions[1]) && !ifInCvv(positions[2]))
                continue;

            if(ifBackFace(views))
                continue;

            // 转换到平面坐标
            QVector3D screenPosition[3];
            for(int k = 0; k < 3; ++k)
            {
                screenPosition[k] = positions[k].toVector3DAffine();
                screenPosition[k].setX((screenPosition[k].x() + 1) * 0.5f * (float)Config::width());
                screenPosition[k].setY((screenPosition[k].y() + 1) * 0.5f * (float)Config::height());
            }

            // 光栅化
            auto drawOnBuffer = std::function([&](int x, int y, uint8_t r, uint8_t g, uint8_t b)
                                              {
                                                  if (x >= Config::width() || x < 0 || y >= Config::height() || y < 0)
                                                      return;
                                                  size_t index = y * Config::width() + x;
                                                  screenBuffer[index * 3 + 0] = r;
                                                  screenBuffer[index * 3 + 1] = g;
                                                  screenBuffer[index * 3 + 2] = b;
                                              });
            if (Config::isWireFrameMode())
            {
                auto bresenhamLineDraw = std::function([](
                                float x1_, float y1_,
                                float x2_, float y2_,
                                const QColor& color,
                                const std::function<void(int, int, uint8_t, uint8_t, uint8_t)>& drawOnBuffer)
                {
                    int x1 = (int)std::round(x1_);
                    int y1 = (int)std::round(y1_);
                    int x2 = (int)std::round(x2_);
                    int y2 = (int)std::round(y2_);

                    int dx = abs(x1 - x2);
                    int dy = abs(y1 - y2);
                    int sx = x1 < x2 ? 1 : -1;
                    int sy = y1 < y2 ? 1 : -1;

                    // 原点
                    if(dx == 0 && dy == 0)
                    {
                        drawOnBuffer(x1, y1, color.red(), color.green(), color.blue());
                        return;
                    }
                    // y轴
                    if(dy == 0)
                    {
                        for (int k = 0; k <= dx; ++k)
                            drawOnBuffer(x1 + sx * k, y1, color.red(), color.green(), color.blue());
                        return;
                    }
                    // x轴
                    if(dx == 0)
                    {
                        for (int k = 0; k <= dy; ++k)
                            drawOnBuffer(x1, y1 + sy * k, color.red(), color.green(), color.blue());
                        return;
                    }
                    // 45度线
                    if(dx == dy)
                    {
                        for (int k = 0; k < dx; ++k)
                            drawOnBuffer(x1 + sx * k, y1 + sy * k, color.red(), color.green(), color.blue());
                        return;
                    }
                    // 贴近x轴
                    if(dx > dy)
                    {
                        int lastY = y1;
                        for (int k = 0; k < dx; ++k)
                        {
                            bool plus1 = abs(2 * sy * dy * k + 2 * y1 * dx - 2 * lastY * dx) >= dx;
                            if(plus1)
                                lastY += sy;
                            drawOnBuffer(x1 + sx * k, lastY, color.red(), color.green(), color.blue());
                        }
                        return;
                    }
                    else
                    {
                        int lastX = x1;
                        for (int k = 0; k < dy; ++k)
                        {
                            bool plus1 = abs(2 * sx * dx * k + 2 * x1 * dy - 2 * lastX * dy) > dy;
                            if (plus1)
                                lastX += sx;
                            drawOnBuffer(lastX, y1 + sy * k, color.red(), color.green(), color.blue());
                        }
                        return;
                    }
                });

                bresenhamLineDraw(
                        screenPosition[0].x(), screenPosition[0].y(),
                        screenPosition[1].x(), screenPosition[1].y(),
                        QColor(255, 255, 255), drawOnBuffer);
                bresenhamLineDraw(
                        screenPosition[1].x(), screenPosition[1].y(),
                        screenPosition[2].x(), screenPosition[2].y(),
                        QColor(255, 255, 255), drawOnBuffer);
                bresenhamLineDraw(
                        screenPosition[2].x(), screenPosition[2].y(),
                        screenPosition[0].x(), screenPosition[0].y(),
                        QColor(255, 255, 255), drawOnBuffer);
                continue;
            }

            if (mesh.GetType() == Mesh::Type::texture)
            {
                QVector2D uvs[3];
                for (int k = 0; k < 3; ++k)
                {
                    auto uvp = mesh.GetTriangleVertex(i, k).GetUV();
                    uvs[k] = QVector2D(uvp[0], uvp[1]);
                }
                auto rasterization = std::function([drawOnBuffer, texture](QVector3D* screenPoss, QVector2D* uv)
                {
                    auto GetIntermediatePoint = std::function
                            ([](const QVector3D &topPos, const QVector3D &bottomPos,
                                const QVector2D &topUV, const QVector2D &bottomUV,
                                float y, QVector3D &outPos, QVector2D &outUV)
                            {
                                float p = (y - topPos.y()) / (bottomPos.y() - topPos.y());
                                p = std::clamp(p, 0.f, 1.f);
                                float x = topPos.x() + p * (bottomPos.x() - topPos.x());
                                float z = topPos.z() + p * (bottomPos.z() - topPos.z());
                                float u = topUV.x() + p * (bottomUV.x() - topUV.x());
                                float v = topUV.y() + p * (bottomUV.y() - topUV.y());
                                outPos = QVector3D(x, y, z);
                                outUV = QVector2D(u, v);
                            });
                    auto drawScanLine = std::function(
                            [drawOnBuffer, texture](int y, QVector3D& lp, QVector3D& rp, QVector2D& luv, QVector2D& ruv)
                            {
                                for (int x = (int)std::round(lp.x()); x < (int)std::round(rp.x()); ++x)
                                {
                                    auto uv = ((float)x - lp.x()) / (rp.x() - lp.x()) * (ruv - luv) + luv;
                                    auto c = texture.GetPixel(uv.x(), uv.y());
                                    drawOnBuffer(x, y, c.red(), c.green(), c.blue());
                                }
                            });
                    auto drawFlatTop = std::function
                            ([GetIntermediatePoint, drawScanLine]
                            (const QVector3D &sp0, const QVector3D &sp1, const QVector3D &sp2,
                             const QVector2D &uv0, const QVector2D &uv1, const QVector2D &uv2)
                            {
                                for (int k = (int)std::round(sp0.y()); k < (int)std::round(sp2.y()); ++k)
                                {
                                    QVector3D lp, rp;
                                    QVector2D luv, ruv;
                                    GetIntermediatePoint(sp0, sp2, uv0, uv2, (float)k, lp, luv);
                                    GetIntermediatePoint(sp1, sp2, uv1, uv2, (float)k, rp, ruv);
                                    drawScanLine(k, lp, rp, luv, ruv);
                                }
                            });
                    auto drawFlatBottom = std::function
                            ([GetIntermediatePoint, drawScanLine]
                            (const QVector3D &sp0, const QVector3D &sp1, const QVector3D &sp2,
                             const QVector2D &uv0, const QVector2D &uv1, const QVector2D &uv2)
                            {
                                for (int k = (int)std::round(sp0.y()); k < (int)std::round(sp2.y()); ++k)
                                {
                                    QVector3D lp, rp;
                                    QVector2D luv, ruv;
                                    GetIntermediatePoint(sp0, sp1, uv0, uv1, (float)k, rp, ruv);
                                    GetIntermediatePoint(sp0, sp2, uv0, uv2, (float)k, lp, luv);
                                    drawScanLine(k, lp, rp, luv, ruv);
                                }
                            });
                    if (screenPoss[0].y() == screenPoss[1].y())
                    {
                        if (screenPoss[2].y() < screenPoss[0].y())
                        {
                            drawFlatBottom(screenPoss[2], screenPoss[0], screenPoss[1], uv[2], uv[0], uv[1]);
                            return;
                        }
                        drawFlatTop(screenPoss[0], screenPoss[1], screenPoss[2], uv[0], uv[1], uv[2]);
                        return;
                    }
                    if (screenPoss[0].y() == screenPoss[2].y())
                    {
                        if (screenPoss[1].y() < screenPoss[0].y())
                        {
                            drawFlatBottom(screenPoss[1], screenPoss[2], screenPoss[0], uv[1], uv[2], uv[0]);
                            return;
                        }
                        drawFlatTop(screenPoss[2], screenPoss[0], screenPoss[1], uv[2], uv[0], uv[1]);
                        return;
                    }
                    if (screenPoss[1].y() == screenPoss[2].y())
                    {
                        if (screenPoss[0].y() < screenPoss[1].y())
                        {
                            drawFlatBottom(screenPoss[0], screenPoss[1], screenPoss[2], uv[0], uv[1], uv[2]);
                            return;
                        }
                        drawFlatTop(screenPoss[1], screenPoss[2], screenPoss[0], uv[1], uv[2], uv[0]);
                        return;
                    }
                    // screenPos: bottom, ml, mr, top
                    QVector3D screenInOrder[4];
                    QVector2D uvInOrder[4];

                    do
                    {
                        if (screenPoss[0].y() < screenPoss[1].y())
                        {
                            if (screenPoss[2].y() < screenPoss[0].y())
                            {
                                screenInOrder[0] = screenPoss[1];
                                screenInOrder[3] = screenPoss[2];
                                uvInOrder[0] = uv[1];
                                uvInOrder[3] = uv[2];
                                QVector3D om_p;
                                QVector2D om_uv;
                                GetIntermediatePoint(screenInOrder[0], screenInOrder[3],
                                                     uvInOrder[0], uvInOrder[3],
                                                     screenPoss[0].y(), om_p, om_uv);
                                if (om_p.x() > screenPoss[0].x())
                                {
                                    screenInOrder[1] = screenPoss[0];
                                    screenInOrder[2] = om_p;
                                    uvInOrder[1] = uv[0];
                                    uvInOrder[2] = om_uv;
                                    break;
                                }
                                screenInOrder[1] = om_p;
                                screenInOrder[2] = screenPoss[0];
                                uvInOrder[1] = om_uv;
                                uvInOrder[2] = uv[0];
                                break;
                            }
                            if (screenPoss[2].y() < screenPoss[1].y())
                            {
                                screenInOrder[0] = screenPoss[1];
                                screenInOrder[3] = screenPoss[0];
                                uvInOrder[0] = uv[1];
                                uvInOrder[3] = uv[0];
                                QVector3D om_p;
                                QVector2D om_uv;
                                GetIntermediatePoint(screenInOrder[0], screenInOrder[3],
                                                     uvInOrder[0], uvInOrder[3],
                                                     screenPoss[2].y(), om_p, om_uv);
                                if (om_p.x() > screenPoss[2].x())
                                {
                                    screenInOrder[1] = screenPoss[2];
                                    screenInOrder[2] = om_p;
                                    uvInOrder[1] = uv[2];
                                    uvInOrder[2] = om_uv;
                                    break;
                                }
                                screenInOrder[1] = om_p;
                                screenInOrder[2] = screenPoss[2];
                                uvInOrder[1] = om_uv;
                                uvInOrder[2] = uv[2];
                                break;
                            }
                            screenInOrder[0] = screenPoss[2];
                            screenInOrder[3] = screenPoss[0];
                            uvInOrder[0] = uv[2];
                            uvInOrder[3] = uv[0];
                            QVector3D om_p;
                            QVector2D om_uv;
                            GetIntermediatePoint(screenInOrder[0], screenInOrder[3],
                                                 uvInOrder[0], uvInOrder[3],
                                                 screenPoss[1].y(), om_p, om_uv);
                            if (om_p.x() > screenPoss[1].x())
                            {
                                screenInOrder[1] = screenPoss[1];
                                screenInOrder[2] = om_p;
                                uvInOrder[1] = uv[1];
                                uvInOrder[2] = om_uv;
                                break;
                            }
                            screenInOrder[1] = om_p;
                            screenInOrder[2] = screenPoss[1];
                            uvInOrder[1] = om_uv;
                            uvInOrder[2] = uv[1];
                            break;
                        }
                        if (screenPoss[2].y() < screenPoss[1].y())
                        {
                            screenInOrder[0] = screenPoss[0];
                            screenInOrder[3] = screenPoss[2];
                            uvInOrder[0] = uv[0];
                            uvInOrder[3] = uv[2];
                            QVector3D om_p;
                            QVector2D om_uv;
                            GetIntermediatePoint(screenInOrder[0], screenInOrder[3],
                                                 uvInOrder[0], uvInOrder[3],
                                                 screenPoss[1].y(), om_p, om_uv);
                            if (om_p.x() > screenPoss[1].x())
                            {
                                screenInOrder[1] = screenPoss[1];
                                screenInOrder[2] = om_p;
                                uvInOrder[1] = uv[1];
                                uvInOrder[2] = om_uv;
                                break;
                            }
                            screenInOrder[1] = om_p;
                            screenInOrder[2] = screenPoss[1];
                            uvInOrder[1] = om_uv;
                            uvInOrder[2] = uv[1];
                            break;
                        }
                        if (screenPoss[2].y() < screenPoss[0].y())
                        {
                            screenInOrder[0] = screenPoss[0];
                            screenInOrder[3] = screenPoss[1];
                            uvInOrder[0] = uv[0];
                            uvInOrder[3] = uv[1];
                            QVector3D om_p;
                            QVector2D om_uv;
                            GetIntermediatePoint(screenInOrder[0], screenInOrder[3],
                                                 uvInOrder[0], uvInOrder[3],
                                                 screenPoss[2].y(), om_p, om_uv);
                            if (om_p.x() > screenPoss[2].x())
                            {
                                screenInOrder[1] = screenPoss[2];
                                screenInOrder[2] = om_p;
                                uvInOrder[1] = uv[2];
                                uvInOrder[2] = om_uv;
                                break;
                            }
                            screenInOrder[1] = om_p;
                            screenInOrder[2] = screenPoss[2];
                            uvInOrder[1] = om_uv;
                            uvInOrder[2] = uv[2];
                            break;
                        }
                        screenInOrder[0] = screenPoss[2];
                        screenInOrder[3] = screenPoss[1];
                        uvInOrder[0] = uv[2];
                        uvInOrder[3] = uv[1];
                        QVector3D om_p;
                        QVector2D om_uv;
                        GetIntermediatePoint(screenInOrder[0], screenInOrder[3],
                                             uvInOrder[0], uvInOrder[3],
                                             screenPoss[0].y(), om_p, om_uv);
                        if (om_p.x() > screenPoss[0].x())
                        {
                            screenInOrder[1] = screenPoss[0];
                            screenInOrder[2] = om_p;
                            uvInOrder[1] = uv[0];
                            uvInOrder[2] = om_uv;
                            break;
                        }
                        screenInOrder[1] = om_p;
                        screenInOrder[2] = screenPoss[0];
                        uvInOrder[1] = om_uv;
                        uvInOrder[2] = uv[0];
                        break;
                    } while (false);

                    drawFlatBottom(screenInOrder[3], screenInOrder[2], screenInOrder[1],
                                   uvInOrder[3], uvInOrder[2], uvInOrder[1]);
                    drawFlatTop(screenInOrder[1], screenInOrder[2], screenInOrder[0],
                                uvInOrder[1], uvInOrder[2], uvInOrder[0]);
                    return;
                });
                rasterization(screenPosition, uvs);
            }
            else if(mesh.GetType() == Mesh::Type::color)
            {
                // 未完成
            }
        }
        Flush();
        QThread::msleep(33);
    }
}

void WorkerThread::Stop()
{
    ShouldStop = true;
}

void WorkerThread::ClearZBuffer()
{
    for (int i = 0; i < pixelTotal; ++i)
        zBuffer[i] = floatMax;
}

void WorkerThread::ClearScreenBuffer(const QColor& c)
{
    for (int i = 0; i < pixelTotal; ++i)
    {
        screenBuffer[i * 3 + 0] = c.red();
        screenBuffer[i * 3 + 1] = c.green();
        screenBuffer[i * 3 + 2] = c.blue();
    }
}
